// UsbEject version 1.0 March 2006
// written by Simon Mourier <email: simon [underscore] mourier [at] hotmail [dot] com>

namespace UsbEject.Library
{
	using System;
	using System.Collections.Generic;
	using System.Collections.ObjectModel;
	using System.ComponentModel;
	using System.Runtime.InteropServices;
	using System.Text;

    /// <summary>
    /// A volume device.
    /// </summary>
    public class Volume : Device, IComparable
    {
        private string volumeName;
        private string logicalDrive;
        private int[] diskNumbers;
        private List<Device> disks;
        private List<Device> removableDevices;

        internal Volume(DeviceClass deviceClass, Native.SP_DEVINFO_DATA deviceInfoData, string path, int index)
            : base(deviceClass, deviceInfoData, path, index)
        {
        }

        /// <summary>
        /// Gets the volume's name.
        /// </summary>
        public string VolumeName
        {
            get
            {
				if (string.IsNullOrEmpty(this.volumeName))
                {
                    StringBuilder sb = new StringBuilder(1024);
                    if (!Native.GetVolumeNameForVolumeMountPoint(Path + "\\", sb, sb.Capacity))
                    {
                        // throw new Win32Exception(Marshal.GetLastWin32Error());
                    }

                    if (sb.Length > 0)
                    {
						this.volumeName = sb.ToString();
                    }
                }

				return this.volumeName;
            }
        }

        /// <summary>
        /// Gets the volume's logical drive in the form [letter]:\
        /// </summary>
        public string LogicalDrive
        {
            get
            {
				if (string.IsNullOrEmpty(this.logicalDrive) && this.VolumeName != null)
                {
					((VolumeDeviceClass)DeviceClass).logicalDrives.TryGetValue(this.VolumeName, out this.logicalDrive);
                }

				return this.logicalDrive;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this volume is a based on USB devices.
        /// </summary>
        public override bool IsUsb
        {
            get
            {
                if (this.Disks != null)
                {
                    foreach (Device disk in this.Disks)
                    {
						if (disk.IsUsb)
						{
							return true;
						}
                    }
                }

                return false;
            }
        }

        /// <summary>
        /// Gets a list of underlying disks for this volume.
        /// </summary>
        public ReadOnlyCollection<Device> Disks
        {
            get
            {
				if (this.disks == null)
                {
					this.disks = new List<Device>();

                    if (this.DiskNumbers != null)
                    {
                        DiskDeviceClass diskDevices = new DiskDeviceClass();
                        foreach (int index in this.DiskNumbers)
                        {
							if (index < diskDevices.Devices.Count)
                            {
								this.disks.Add(diskDevices.Devices[index]);
                            }
                        }
                    }
                }

				return new ReadOnlyCollection<Device>(this.disks);
            }
        }

        private int[] DiskNumbers
        {
            get
            {
				if (this.diskNumbers == null)
                {
                    List<int> numbers = new List<int>();
                    if (this.LogicalDrive != null)
                    {
                        IntPtr hFile = Native.CreateFile(@"\\.\" + this.LogicalDrive, Native.GENERIC_READ, Native.FILE_SHARE_READ | Native.FILE_SHARE_WRITE, IntPtr.Zero, Native.OPEN_EXISTING, 0, IntPtr.Zero);
						
						if (hFile.ToInt32() == Native.INVALID_HANDLE_VALUE)
						{
							int errorCode = Marshal.GetLastWin32Error();
							
							throw new Win32Exception(errorCode);
						}

                        int size = 0x400; // some big size
                        IntPtr buffer = Marshal.AllocHGlobal(size);
                        int bytesReturned = 0;
                        try
                        {
                            if (!Native.DeviceIoControl(hFile, Native.IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, IntPtr.Zero, 0, buffer, size, out bytesReturned, IntPtr.Zero))
                            {
                                // do nothing here on purpose
                            }
                        }
                        finally
                        {
                            Native.CloseHandle(hFile);
                        }

                        if (bytesReturned > 0)
                        {
                            int numberOfDiskExtents = (int)Marshal.PtrToStructure(buffer, typeof(int));
                            for (int i = 0; i < numberOfDiskExtents; i++)
                            {
                                IntPtr extentPtr = new IntPtr(buffer.ToInt32() + Marshal.SizeOf(typeof(long)) + i * Marshal.SizeOf(typeof(Native.DISK_EXTENT)));
                                Native.DISK_EXTENT extent = (Native.DISK_EXTENT)Marshal.PtrToStructure(extentPtr, typeof(Native.DISK_EXTENT));
                                numbers.Add(extent.DiskNumber);
                            }
                        }

                        Marshal.FreeHGlobal(buffer);
                    }

					this.diskNumbers = new int[numbers.Count];
					numbers.CopyTo(this.diskNumbers);
                }

				return this.diskNumbers;
            }
        }

        /// <summary>
        /// Gets a list of removable devices for this volume.
        /// </summary>
		public override List<Device> RemovableDevices
        {
            get
            {
				if (this.removableDevices == null)
                {
					this.removableDevices = new List<Device>();
                    if (this.Disks == null)
                    {
						this.removableDevices = base.RemovableDevices;
                    }
                    else
                    {
                        foreach (Device disk in this.Disks)
                        {
                            foreach (Device device in disk.RemovableDevices)
                            {
								this.removableDevices.Add(device);
                            }
                        }
                    }
                }

				return this.removableDevices;
            }
        }

        /// <summary>
        /// Compares the current instance with another object of the same type.
        /// </summary>
        /// <param name="obj">An object to compare with this instance.</param>
        /// <returns>A 32-bit signed integer that indicates the relative order of the comparands.</returns>
        public override int CompareTo(object obj)
        {
            Volume device = obj as Volume;
			if (device == null)
			{
				throw new ArgumentNullException("obj", "Comparison object cannot be null");
			}

			if (this.LogicalDrive == null)
			{
				return 1;
			}

			if (device.LogicalDrive == null)
			{
				return -1;
			}

			return this.LogicalDrive.CompareTo(device.LogicalDrive);
        }
    }
}
